from vectors import *

# This file contatins the required methods and classes for declaring objects
# in the engine. It contains classes for Obstacles, Lines and Rays


# Line Box Intersection Fixed! Since we do not care of the transformation	
# of the object or the line, we can simply use the object space of the 
# coordinates of the given objects and lines
def CheckLineInBox(L, Box):
	"""Checks to see if the line interesects the box in any way. The first
	element must be line object and the second element is an ObjectEntity.i
	Returns 0 if the line does not intersect, otherwise, it returns the
	FIRST intersecting point with the box. Simply revert the line in order
	to get where it exits."""
	if 	(not isinstance(L, Line)) or (not isinstance(Box, ObjectEntity)):
			raise TypeError("Expected Two Object Entities as Arguments")

	def getIntersection(fDst1, fDst2, line):
		"""Checks to see if the line intersects the box given the distances
		between each point of the line and the point for the bounding boxes"""
		if ((fDst1 * fDst2) >= 0.0):
			return 0
		if (fDst1 == fDst2):
			return 0
		hit = line.pt1 + (line.pt2 - line.pt1) * (-fDst1/(fDst2 - fDst1))
		return hit

	def inBox(hit, box, axis):
		"""Checks to see if the intersection point is in side the box"""
		if (axis == 1 and hit.z >= box.pt1.z and hit.z <= box.pt2.z and \
			hit.y >= box.pt1.y and hit.y <= box.pt2.y):
			return True
		if (axis == 2 and hit.z >= box.pt1.z and hit.z <= box.pt2.z and \
			hit.x >= box.pt1.x and hit.x <= box.pt2.x):
			return True
		if (axis == 3 and hit.x >= box.pt1.x and hit.x <= box.pt2.x and \
			hit.y >= box.pt1.y and hit.y <= box.pt2.y):
			return True

		return False
	
	if (L.pt1.x < Box.pt1.x and L.pt2.x < Box.pt1.x):
		return ()
	if (L.pt1.x > Box.pt2.x and L.pt2.x > Box.pt2.x):
		return ()
	if (L.pt1.y < Box.pt1.y and L.pt2.y < Box.pt1.y):
		return ()
	if (L.pt1.y > Box.pt2.y and L.pt2.y > Box.pt2.y):
		return ()
	if (L.pt1.z < Box.pt1.z and L.pt2.z < Box.pt1.z):
		return ()
	if (L.pt1.z > Box.pt2.z and L.pt2.z > Box.pt2.z):
		return ()
	
	pts = []
	
	if isPtInBox(L.pt1, Box):
		pts.append(L.pt1)

	if isPtInBox(L.pt2, Box):
		pts.append(L.pt2)
	
	pt = getIntersection(L.pt1.x - Box.pt1.x, L.pt2.x - Box.pt1.x, L)
	if (pt != 0) and inBox(pt, Box, 1):
		pts.append(pt)
	pt = getIntersection(L.pt1.y - Box.pt1.y, L.pt2.y - Box.pt1.y, L)
	if (pt != 0) and inBox(pt, Box, 2):
		pts.append(pt)
	pt = getIntersection(L.pt1.z - Box.pt1.z, L.pt2.z - Box.pt1.z, L)
	if (pt != 0) and inBox(pt, Box, 3):
		pts.append(pt)
	pt = getIntersection(L.pt1.x - Box.pt2.x, L.pt2.x - Box.pt2.x, L)
	if (pt != 0) and inBox(pt, Box, 1):
		pts.append(pt)
	pt = getIntersection(L.pt1.y - Box.pt2.y, L.pt2.y - Box.pt2.y, L)
	if (pt != 0) and inBox(pt, Box, 2):
		pts.append(pt)
	pt = getIntersection(L.pt1.z - Box.pt1.z, L.pt2.z - Box.pt1.z, L)
	if (pt != 0) and inBox(pt, Box, 3):
		pts.append(pt)
	
	return tuple(pts)

def isPtInBox(coord, box):
	"""Checks to see if the coordinate is enclosed within a box. The function
	takes a coordinate as the first argument as a vec3 object, and second
	argument is a box in the form of ObjectEntity. Also, it must be that the
	first corner of the box is strictly less than the second corner of the box,
	meaning that all elements are less than the second corner."""
	if not isinstance(coord, vec3):
		raise TypeError("Expected a vec3 object as the coordinate")
	if not isinstance(box, ObjectEntity):
		raise TypeError("Expected a object entity object as the box")

	if coord.x < box.pt1.x or coord.y < box.pt1.y or coord.z < box.pt1.z:
		return False

	if coord.x > box.pt2.x or coord.y > box.pt2.y or coord.z > box.pt2.z:
		return False

	return True

class ObjectEntity:
	"""The base class from which Obstacles and other objects with area inherit
	from. Definitely all these objects have a positive volume, thus
	must be initialized with position list containing two 3-dimensional 
	coordinates representing the obstacle's each corner point.
	For the sake of commonality, first element is lower corner, second 
	element is the top corner of the object"""
	
	name = None
	def __init__(self, id, pt1, pt2, velocity=(0,0,0)):
		if not ((type(id) is int) or (type(id) is long)):
			raise TypeError("Expected integer or long for id, instead got " + \
			                str(type(id)))
		if not is_vec3_friendly(pt1) or not is_vec3_friendly(pt2):
			raise ValueError("Expected something convertible to vec3" + \
			                 " for points, instead got " + str(position))
		if not is_vec3_friendly(velocity):
			raise ValueError("Expected something convertible to vec3 for " + \
			                 "velocity, instead got " + str(velocity))
		self.id = id
		self.pt1 = vec3(min(pt1[0], pt2[0]),
						min(pt1[1], pt2[1]),
						min(pt1[2], pt2[2]))
		self.pt2 = vec3(max(pt1[0], pt2[0]),
						max(pt1[1], pt2[1]),
						max(pt1[2], pt2[2]))
		self.v = vec3(velocity)

	def __repr__(self):
		"""How the ObjectEntity sees itself."""
		# The idea here is to dig out the term ObjectEntity, or whatever other
		# inheriting class name it is, in order to only write __repr__ once.
		temp = str(self.__class__)
		namestr = ''
		if self.name:
			namestr = " (%s)" % self.name
		return ("%s %d" % (temp[temp.find('.')+1:], self.id)) + namestr
		
		#TODO: Other functions universal to the Objects

	def volume(self):
		"""Returns the volume of the object. The unit is in 1 unit of 
		coordinate cubed"""
		length = abs(self.pt2.x - self.pt1.x)
		width = abs(self.pt2.y - self.pt1.y)
		height = abs(self.pt2.z - self.pt1.z)
		return length * width * height

	def isIntersecting(self, obj):
		"""Checks to see if two object entities are intersecting in any way.
		Make sure that the second object is an Object rather than a line or ray.
		"""
		if 	(not isinstance(obj, ObjectEntity)):
				raise TypeError("Expected Object Entities as Arguments")
	
		x1y1z1 = obj.pt1
		x1y1z2 = vec3(obj.pt1.x, obj.pt1.y, obj.pt2.z)
		x1y2z1 = vec3(obj.pt1.x, obj.pt2.y, obj.pt1.z)
		x1y2z2 = vec3(obj.pt1.x, obj.pt2.y, obj.pt2.z)
		x2y1z1 = vec3(obj.pt2.x, obj.pt1.y, obj.pt1.z)
		x2y1z2 = vec3(obj.pt2.x, obj.pt1.y, obj.pt2.z)
		x2y2z1 = vec3(obj.pt2.x, obj.pt2.y, obj.pt1.z)
		x2y2z2 = obj.pt2

		if 	isPtInBox(x1y1z1, self) or isPtInBox(x1y1z2, self) or \
			isPtInBox(x1y2z1, self) or isPtInBox(x1y2z2, self) or \
			isPtInBox(x2y1z1, self) or isPtInBox(x2y1z2, self) or \
			isPtInBox(x2y2z1, self) or isPtInBox(x2y2z2, self):

			return True

		return False
	
class Obstacle(ObjectEntity):
	"""The class used to represent the obstacles that are used to sheild the
	radiation from the radiation source. """
	
	name = None
	
	def __init__(self, id, pt1, pt2, velocity=(0,0,0), obj_type=None):
		if not ((type(id) is int) or (type(id) is long)):
			raise TypeError("Expected integer or long for id, instead got " + \
			                str(type(id)))
		if not is_vec3_friendly(pt1) or not is_vec3_friendly(pt2):
			raise ValueError("Expected something convertible to vec3" + \
			                 " for pts, instead got " + str(position))
		if not is_vec3_friendly(velocity):
			raise ValueError("Expected something convertible to vec3 for " + \
			                 "velocity, instead got " + str(velocity))
		if obj_type is not None:
			if type(obj_type) is not str:
				raise TypeError("Require a String as Type")
			if obj_type not in \
					("Air", "Aluminum","Water","Carbon","Iron","Copper", 
					"Concrete"):
				raise ValueError("Requires one of the standard radiation Types")
		else:
			self.type = "Air"

		self.id = id
		self.pt1 = vec3(min(pt1[0], pt2[0]),
							min(pt1[1], pt2[1]),
							min(pt1[2], pt2[2]))
		self.pt2 = vec3(max(pt1[0], pt2[0]),
							max(pt1[1], pt2[1]),
							max(pt1[2], pt2[2]))
		self.v = vec3(velocity)
		# Should include type and density of the material
		self.type = obj_type


class Line(ObjectEntity):
	"""The class used to represent the line segments that are used for certain
	purposes. Takes inputs as pt1ing and pt2ing point."""

	name = None
	
	def __init__(self, id, pt1, pt2, velocity=(0,0,0)):
		if not ((type(id) is int) or (type(id) is long)):
			raise TypeError("Expected integer or long for id, instead got " + \
			                str(type(id)))
		if not is_vec3_friendly(pt1) or not is_vec3_friendly(pt2):
			raise ValueError("Expected something convertible to vec3" + \
			                 " for pts, instead got " + str(position))
		if not is_vec3_friendly(velocity):
			raise ValueError("Expected something convertible to vec3 for " + \
			                 "velocity, instead got " + str(velocity))
		self.id = id
		self.pt1 = vec3(pt1)
		self.pt2 = vec3(pt2)
		self.v = vec3(velocity)

	def length(self):
		"""Returns the length of the diagonal characterized by the two 
		coordinates."""
		mag = (	(self.pt2.x - self.pt1.x)**2 +
				(self.pt2.y - self.pt1.y)**2 +
				(self.pt2.z - self.pt1.z)**2)**.5
		return mag
	
	def volume(self):
		return 0

class Ray(ObjectEntity):
	"""The class used to represent the rays that are used for certain
	purposes."""

	name = None
	
	def __init__(self, id, pt1, pt2, velocity=(0,0,0)):
		if not ((type(id) is int) or (type(id) is long)):
			raise TypeError("Expected integer or long for id, instead got " + \
			                str(type(id)))
		if not is_vec3_friendly(pt1) or not is_vec3_friendly(pt2):
			raise ValueError("Expected something convertible to vec3" + \
			                 " for pts, instead got " + str(position))
		if not is_vec3_friendly(velocity):
			raise ValueError("Expected something convertible to vec3 for " + \
			                 "velocity, instead got " + str(velocity))
		self.id = id
		self.pt1 = vec3(pt1)
		self.pt2 = vec3(pt2)
		self.v = vec3(velocity)

	def volume(self):
		return 0
